package be.ipl.finito.domaine;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.MapKeyColumn;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

import be.ipl.finito.exceptions.ActionImpossibleException;
import be.ipl.finito.exceptions.JoueurDejaPresentException;

@SuppressWarnings("serial")
@Entity
@Table(name = "PARTIES", schema = "FINITO")
public class Partie implements Serializable {

	@Transient
	public final int MAX_JOUEURS = Constantes.MAX_JOUEURS;
	@Transient
	public final int MIN_JOUEURS = Constantes.MIN_JOUEURS;
	@Transient
	public final int TEMPS_DEBUT_PARTIE = Constantes.TEMPS_DEBUT_PARTIE;
	@Transient
	public final int TEMPS_INACTIVITE = Constantes.TEMPS_INACTIVITE;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id_partie;

	@NotNull
	@Column(unique = true)
	private String nom;

	@NotNull
	@Enumerated
	private Etat etat;

	@NotNull
	@Min(0)
	@Max(12)
	private int indice_tirage;

	@NotNull
	private int tour;

	@Transient
	private List<Plateau> plateaux;

	@ManyToMany
	@JoinTable(name = "TIRAGES", schema = "FINITO", joinColumns = { @JoinColumn(name = "PARTIE") }, inverseJoinColumns = { @JoinColumn(name = "JETON") })
	@MapKeyColumn(name = "PLACE")
	private Map<Integer, Jeton> placeJetons = new HashMap<Integer, Jeton>();

	private int resultatDe;

	protected Partie() {
	}

	public Partie(String nom, Joueur joueur, List<Jeton> listeJetonsShuffle) {
		// initialize class variables
		plateaux = new ArrayList<Plateau>();
		placeJetons = new HashMap<Integer, Jeton>();
		this.nom = nom;
		tour = 0;

		for (int i = 0; i < listeJetonsShuffle.size(); i++) {
			placeJetons.put(i, listeJetonsShuffle.get(i));
		}

		// je cree un premier plateau
		Plateau p = new Plateau(joueur, this);

		// j'assigne le plateau au joueur
		joueur.ajouterPlateau(p);

		// j'ajoute le plateau dans ma liste de plateaux
		plateaux.add(p);

		// la partie est en etat initial
		etat = Etat.INITIAL;
	}

	public String getNom() {
		return nom;
	}

	public void setNom(String nom) {
		this.nom = nom;
	}

	public Etat getEtat() {
		return etat;
	}

	public void setEtat(Etat etat) {
		this.etat = etat;
	}

	public int getIndiceTirage() {
		return indice_tirage;
	}

	public void setIndiceTirage(int indiceTirage) {
		this.indice_tirage = indiceTirage;
	}

	public List<Plateau> getPlateaux() {
		return plateaux;
	}

	public void setPlateaux(List<Plateau> plateaux) {
		this.plateaux = plateaux;
	}

	public void setResultatDe(int resultatDe) {
		this.resultatDe = resultatDe;
	}

	public int getResultatDe() {
		return resultatDe;
	}

	public Map<Integer, Jeton> getPlaceJetons() {
		return placeJetons;
	}

	public Jeton getProchainJeton() {
		return placeJetons.get(indice_tirage);
	}

	public void setPlaceJetons(List<Jeton> jetons) {
		int i = 0;
		Map<Integer, Jeton> placeJetonsTmp = new HashMap<Integer, Jeton>();
		for (Jeton j : jetons) {
			placeJetonsTmp.put(i, j);
		}
		placeJetons.putAll(placeJetonsTmp);
	}

	public int getMAX_JOUEURS() {
		return MAX_JOUEURS;
	}

	public int getMIN_JOUEURS() {
		return MIN_JOUEURS;
	}

	public int getTEMPS_DEBUT_PARTIE() {
		return TEMPS_DEBUT_PARTIE;
	}

	public int getTEMPS_INACTIVITE() {
		return TEMPS_INACTIVITE;
	}

	public int getId_partie() {
		return id_partie;
	}

	
	public enum Etat {
		INITIAL {
			@Override
			Plateau ajouterJoueur(Joueur joueur, Partie partie)
					throws JoueurDejaPresentException {
				// je verifie que la partie ne contient pas deja ce joueur
				if (partie.plateaux == null) {
					partie.plateaux = new ArrayList<Plateau>();
				}
				for (Plateau p : partie.plateaux) {
					if (p.getJoueur().getIdJoueur() == joueur.getIdJoueur()) {
						throw new JoueurDejaPresentException(
								"Le joueur est deja dans la partie");
					}
				}
				// je cree le plateau associe au joueur
				Plateau plateau = new Plateau(joueur, partie);
				
				// je rajoute le plateau dans la liste des plateaux de la partie
				joueur.ajouterPlateau(plateau);
				
				// partie.ajouterPlateau(plateau);
				partie.plateaux.add(plateau);
				
				return plateau;
			}

			@Override
			boolean enleverJoueur(Joueur joueur, Partie partie) {
				return partie.plateaux.remove(joueur);
			}

			@Override
			Partie lancerPartie(Partie partie) throws ActionImpossibleException {
				if (partie.plateaux.size() < partie.MIN_JOUEURS) {
					throw new ActionImpossibleException(
							"Il n'y a pas assez de joueurs pour lancer la partie!");
				}
				if (partie.plateaux.size() >= partie.MIN_JOUEURS) {
					partie.etat = Etat.EN_COURS_POSE;
					partie.indice_tirage = 3;

					return partie;
				}
				return partie;
			}

			@Override
			Partie annulerPartie(Partie partie) {
				for (Plateau p : partie.plateaux) {
					p.getJoueur().enleverPlateau(p);
				}
				return partie;
			}
		},

		EN_COURS_POSE {
			@Override
			Partie suspendre(Partie partie) {
				partie.etat = Etat.SUSPENDUE;
				return partie;
			}

			@Override
			Partie commencerTourSuivant(Partie partie) {

				// on tire le jeton suivant
				// je lance le de pour savoir ou il faudra placer le jeton
				partie.resultatDe = partie.lanceDe();
				
				// pour chaque plateau, j'indique quel est le jeton tire
				for (Plateau p : partie.plateaux) {
					p.ajouterJetonAPoser(partie.placeJetons
							.get(partie.indice_tirage));
				}
				if (partie.indice_tirage < 12) {
					partie.indice_tirage = partie.indice_tirage + 1;
				}
				
				// si on n'a plus de jeton a tirer, on passe en phase 2
				if (partie.tour >= 11) {
					partie.etat = Etat.EN_COURS_DEPLACEMENT;
				}
				partie.tour++;
				return partie;

			}

		},

		EN_COURS_DEPLACEMENT {

			@Override
			Partie suspendre(Partie partie) {
				partie.etat = Etat.SUSPENDUE;
				return partie;
			}

			@Override
			Partie commencerTourSuivant(Partie partie) {
				// si au moins 1 joueur a fini sa suite, on termine la partie
				for (Plateau p : partie.plateaux) { 
					if (p.getScore() >= 12) {
						partie.etat = Etat.FINIE;
						return partie;
					}
				}
				// sinon on continue de jouer, donc on lance le de
				partie.resultatDe = partie.lanceDe(); 
				partie.tour++;
				return partie;
			}
		},

		SUSPENDUE {
			/**
			 * reprend la partie qui etait suspendue et la remet dans le bon
			 * etat
			 */
			@Override
			Partie reprendre(Partie partie) {
				// si on a tire 12 jetons la partie est en mode deplacement
				if (partie.indice_tirage >= 12) { 
					partie.etat = Etat.EN_COURS_DEPLACEMENT;
				} else { // sinon, la partie est encore en mode pose
					partie.etat = Etat.EN_COURS_POSE;
				}
				return partie;
			}
		},

		FINIE {
			@Override
			List<Joueur> estVainqueur(Partie partie) {
				List<Joueur> listeVainqueurs = new ArrayList<Joueur>();
				for (Plateau p : partie.plateaux) {
					if (p.getScore() >= 12) {
						listeVainqueurs.add(p.getJoueur());
					}
				}
				return listeVainqueurs;
			}
		};

		List<Joueur> estVainqueur(Partie partie) {
			return new ArrayList<Joueur>();
		}

		Partie suspendre(Partie partie) {
			return partie;
		}

		Partie commencerTourSuivant(Partie partie) {
			return partie;
		}

		int lanceDe() {
			int result = (int) (Math.random() * (20 - 1)) + 1;
			if (result <= 0 || result > 20)
				result = lanceDe();
			return result;
		}

		Plateau ajouterJoueur(Joueur joueur, Partie partie)
				throws JoueurDejaPresentException {
			return null;
		}

		boolean enleverJoueur(Joueur joueur, Partie partie) {
			return false;
		}

		Partie reprendre(Partie partie) {
			return partie;
		}

		Partie lancerPartie(Partie partie) throws ActionImpossibleException {
			return partie;
		}

		Partie annulerPartie(Partie partie) {
			return partie;
		}
	}

	public Plateau ajouterJoueur(Joueur joueur)
			throws JoueurDejaPresentException {
		return etat.ajouterJoueur(joueur, this);
	}

	public boolean enleverJoueur(Joueur joueur) {
		return etat.enleverJoueur(joueur, this);
	}

	public int lanceDe() {
		this.resultatDe = etat.lanceDe();
		return this.resultatDe;
	}

	public Partie commencerTourSuivant() {
		return etat.commencerTourSuivant(this);
	}

	public List<Joueur> estVainqueur() {
		return etat.estVainqueur(this);
	}

	public Partie suspendre() {
		etat.suspendre(this);
		return this;
	}

	public Partie reprendre() {
		etat.reprendre(this);
		return this;
	}

	public Partie lancerPartie() throws ActionImpossibleException {
		return etat.lancerPartie(this);
	}

	public Partie annulerPartie() {
		etat.annulerPartie(this);
		return this;
	}

	public Plateau getPlateau(Joueur joueur) {
		for (Plateau p : plateaux) {
			if (p.getJoueur().getLogin().equals(joueur.getLogin())) {
				return p;
			}
		}
		return null;
	}

	public List<Joueur> getJoueurs() {
		List<Joueur> listJ = new ArrayList<Joueur>();
		for (Plateau pl : plateaux) {
			listJ.add(pl.getJoueur());
		}
		return listJ;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + id_partie;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Partie other = (Partie) obj;
		if (id_partie != other.id_partie)
			return false;
		return true;
	}

	public int getTour() {
		return this.tour;
	}

	public Partie updatePlateau(Plateau plateau) {

		List<Plateau> newPlateaux = new ArrayList<Plateau>();
		for (Plateau p : plateaux) {
			if (p.getJoueur().getLogin()
					.compareTo(plateau.getJoueur().getLogin()) == 0) {
				newPlateaux.add(plateau);
			} else {
				newPlateaux.add(p);

			}
		}
		this.plateaux = newPlateaux;
		return this;

	}

	public Jeton getProchainJeton(int i) {
		return placeJetons.get(i);
	}

	public List<Plateau> getPlateauxTrier() {
		List<Plateau> plateauxTmp = new ArrayList<Plateau>(plateaux);
		List<Plateau> plateauxTrier = new ArrayList<Plateau>();
		Plateau plateauTmp = null;

		for (int i = 0; i < plateaux.size(); i++) {
			int score = -1;
			for (Plateau p : plateauxTmp) {

				if (p.getScore() > score) {
					plateauTmp = p;
				}
			}
			plateauxTmp.remove(plateauTmp);
			plateauxTrier.add(plateauTmp);
		}

		return plateauxTrier;
	}

}
